package oceanlife.pathfinding;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;

import oceanlife.OLSet;

public class DelaunayTriangleTranslator {

	private double maxConvexSpacing = 50.0;
    private static int initialSize = OLSet.border;     // Size of initial triangle
    public static Triangle initialTriangle= new Triangle(
            new Pnt(-initialSize, -initialSize),
            new Pnt( initialSize, -initialSize),
            new Pnt(           0,  initialSize));           // Initial triangle

    private Triangulation dt;                   // Delaunay triangulation
    private Obstacles obstacles;
    
    public DelaunayTriangleTranslator(Obstacles obs){
        dt = new Triangulation(initialTriangle);
        obstacles = obs;
        addPoints();
    }

    public DelaunayTriangleTranslator(){
        dt = new Triangulation(initialTriangle);
    }
    
	public Triangulation getDT() {
		return dt;
	}

	public void makeDelaunayTriangles() {
		conformToConvexSpacing();
		System.out.println("done with doubling");
        boolean[] correct = null;
        int counter = 0;
        int maxcounter = 20;
        boolean twiceCorrect = false;
        while(!twiceCorrect && counter < maxcounter){
        	twiceCorrect = true;
        	correct = getCorrectTriangles();
	        /*while(!allCorrect(correct) && counter < maxcounter){
	        	twiceCorrect = false;
		        counter++;
		        for(int i = 0; i < correct.length;i++){
		        	if(!correct[i]){
		        		System.out.println(i+"/"+correct.length+" not right");
		        		makeCorrection(i);
		        		correct = getCorrectTriangles();
		        		break;
		        	}
		        }
	        }*/
	        //counter = 0;
	        correct = getExtraCorrectTriangles();
	        while(!allCorrect(correct) && counter < maxcounter){
	        	twiceCorrect = false;
		        counter++;
		        for(int i = 0; i < correct.length;i++){
		        	if(!correct[i]){
		        		System.out.println(i+"/"+correct.length+" not right extra correction");
		        		makeCorrection(i);
		        		correct = getExtraCorrectTriangles();
		        		break;
		        	}
		        }
	        }
        }
	}
    private void conformToConvexSpacing() {
		int doubleCounter = 0;
		for(int i = 0;i<obstacles.metaObstacleCount();i++){
			while(convexSpacing(obstacles.getMetaObstacle(i)) > maxConvexSpacing){
				doubleCounter++;
				System.out.println(doubleCounter+" "+convexSpacing(obstacles.getMetaObstacle(i))+"/"+maxConvexSpacing);
				Triangulation metaOb = obstacles.getMetaObstacle(i);
				for(Triangle tri : metaOb){
					int index = obstacles.getIndex(tri);
					if(index >= 0){
						obstacles.remove(index);
					}
				}
				obstacles.getMetaObstacle(i).doubleConvexHull();
				ArrayList<Triangle> newTriangles = extractTriangles(obstacles.getMetaObstacle(i));
				obstacles.addAll(newTriangles);
				this.clear();
				addPoints();
			}
		}

	}

	private double convexSpacing(Triangulation triangulation) {
    	double maxSpace = 0;
    	Pnt[] convexHull = triangulation.getConvexHull();
		for(int i = 0; i < convexHull.length;i++){
			int j = i+1;
			if(j == convexHull.length){
				j = 0;
			}
			if(convexHull[i].distanceTo(convexHull[j]) > maxSpace){
				maxSpace = convexHull[i].distanceTo(convexHull[j]);
			}
		}
		return maxSpace;
	}

	public void addSite(Pnt point) {
        dt.delaunayPlace(point);
    }
    public void clear() {
        dt = new Triangulation(initialTriangle);
    }

	private void makeCorrection(int i) {
		Triangle faulty = obstacles.get(i);
		Triangulation metaOb = obstacles.findMetaOb(faulty);
		for(Triangle tri : metaOb){
			int index = obstacles.getIndex(tri);
			if(index >= 0){
				obstacles.remove(index);
			}
		}
		metaOb.doubleConvexHull();
		ArrayList<Triangle> newTriangles = extractTriangles(metaOb);
		obstacles.addAll(newTriangles);
		this.clear();
		addPoints();
	}
	private boolean allCorrect(boolean[] correct) {
		for(int i = 0; i < correct.length;i++){
			if(!correct[i]){
				return false;
			}
		}
 		return true;
	}

    private boolean[] getExtraCorrectTriangles() {
        boolean[] correct = new boolean[obstacles.size()];
        Arrays.fill(correct,true);
    	ArrayList<Integer> faultyNeighTris = new ArrayList<Integer>();
        for(Triangle test : dt){
        	int index = obstacles.getIndex(test);
        	
        	if(index == -1 ){
        		int faultyTri = -1;
        		for(Triangle obs : obstacles.getObstacles()){
        			if(obs.pointInsideTriangle(test.getCircumcenter())){
        				faultyTri = obstacles.getIndex(obs);
        				break;
        			}
        		}
        		if(faultyTri >= 0 && test.isNeighbor(obstacles.get(faultyTri))){
        			faultyNeighTris.add(faultyTri);
        		}
        	}
        }
        for(int test : faultyNeighTris){
        	//System.out.println(test+" wrong by extra test");
        	correct[test] = false;
        }
	    return correct;
	}

	private boolean[] getCorrectTriangles() {
        boolean[] correct = new boolean[obstacles.size()];
        for(Triangle test : dt){
        	int index = obstacles.getIndex(test);
        	
        	if(index >= 0 ){
        		correct[index] = true;
        	}
        }
	    return correct;
	}
	private void addPoints() {
        HashSet<Pnt> done = new HashSet<Pnt>(DelaunayTriangleTranslator.initialTriangle);
        for(Triangle obs: obstacles.getObstacles()){     	
	        for(Pnt pnt : obs){
                if (done.contains(pnt)) continue;
                done.add(pnt);
	            addSite(pnt);
	        }       
        }
    }

	public static ArrayList<Triangle> extractTriangles(Triangulation dtFaulty) {
		ArrayList<Triangle> tris = new ArrayList<Triangle>();
		for(Triangle newTriangle : dtFaulty){
			boolean internalTriangle = true;
			for(Pnt pnt : newTriangle){
				if(Math.abs(pnt.coord(0)) >= 10000 || Math.abs(pnt.coord(1)) >= 10000){
					internalTriangle = false;
				}
				//System.out.print(pnt.coord(0)+"/"+pnt.coord(1)+" ");
			}
			//System.out.println();
			if(internalTriangle){
				tris.add(newTriangle);
			}
		}
		return tris;
	}

}
